﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CCM.CronTaskScheduler {
    public interface IRunTimeManager {
        #region Data Members (2)

        ICollection<TaskDefinition> ExecutingTasks { get; }

        ICollection<TaskDefinition> PendingTasks { get; }

        #endregion Data Members

        #region Operations (2)

        void EndAllTasks();

        void QueueTask(TaskDefinition task);

        #endregion Operations
    }
    public class RunTimeManager : IRunTimeManager {
        #region Fields (5)

        private INotificationProvider notificationProvider;
        private Queue<TaskDefinition> qGlobalParallel = new Queue<TaskDefinition>();
        private Queue<TaskDefinition> qGlobalSeq = new Queue<TaskDefinition>();
        private Dictionary<string, Queue<TaskDefinition>> qGroupSeq = new Dictionary<string, Queue<TaskDefinition>>();
        private Timer queueTimer;

        #endregion Fields

        #region Constructors (1)

        public RunTimeManager(INotificationProvider notificationProvider) {
            PendingTasks = new List<TaskDefinition>();
            ExecutingTasks = new List<TaskDefinition>();
            this.notificationProvider = notificationProvider;
            this.queueTimer = new Timer(QueueTimer, this, 0, 100);
        }

        #endregion Constructors

        #region Properties (2)

        public ICollection<TaskDefinition> ExecutingTasks { get; private set; }

        public ICollection<TaskDefinition> PendingTasks { get; private set; }

        #endregion Properties

        #region Methods (4)

        // Public Methods (2) 

        public void EndAllTasks() {
            PendingTasks.Clear();
            ExecutingTasks.Clear();
        }

        public void QueueTask(TaskDefinition task) {
            Queue<TaskDefinition> selectedQ = null;
            switch (task.RunMode) {
                case TaskRunMode.GlobalSequential:
                    selectedQ = qGlobalSeq;
                    break;
                case TaskRunMode.GlobalParallel:
                    selectedQ = qGlobalParallel;
                    break;
                case TaskRunMode.GroupSequential:
                    if (!qGroupSeq.ContainsKey(task.GroupName))
                        qGroupSeq.Add(task.GroupName, new Queue<TaskDefinition>());
                    selectedQ = qGroupSeq[task.GroupName];
                    break;
                default:
                    throw new ArgumentException("Unknown TaskRunMode", task.RunMode.ToString());
            }
            PendingTasks.Add(task);
            selectedQ.Enqueue(task);
        }
        // Private Methods (2) 

        private void ExecuteTask(TaskDefinition taskDefinition) {
            PendingTasks.Remove(taskDefinition);
            ExecutingTasks.Add(taskDefinition);
            var executor = Activator.CreateInstance(taskDefinition.ExecutorType) as ITaskExecutor;
            var result = true;
            string message = null;
            Task.Factory.StartNew(() => {
                try {
                    result = executor.RunTask(taskDefinition.TaskType, taskDefinition.CustomData);
                } catch (Exception ex) {
                    result = false;
                    message = ex.Message;
                }
            }).ContinueWith(t => {
                ExecutingTasks.Remove(taskDefinition);
                notificationProvider.RunComplete(new TaskRunResult {
                    Success = result,
                    TaskDefinition = taskDefinition,
                    Message = message
                });
            });
        }

        private static void QueueTimer(object state) {
            var rtm = state as RunTimeManager;
            //
            // Global Parallel
            while (rtm.qGlobalParallel.Count > 0)
                rtm.ExecuteTask(rtm.qGlobalParallel.Dequeue());
            //
            // Global Sequential
            if (rtm.qGlobalSeq.Count > 0 && !rtm.ExecutingTasks.Any(a => a.RunMode == TaskRunMode.GlobalSequential))
                rtm.ExecuteTask(rtm.qGlobalSeq.Dequeue());
            //
            // Group Sequential
            foreach (var group in rtm.qGroupSeq) {
                if (rtm.qGroupSeq.Count > 0 && !rtm.ExecutingTasks.Any(a => a.GroupName == group.Key))
                    rtm.ExecuteTask(rtm.qGroupSeq[group.Key].Dequeue());
            }

        }

        #endregion Methods
    }
}
